Unit mnStreams;
{**
 *  This file is part of the "Mini Library"
 *
 * @license   modifiedLGPL (modified of http://www.gnu.org/licenses/lgpl.html)
 *            See the file COPYING.MLGPL, included in this distribution,
 * @author    Zaher Dirkey <zaher at parmaja dot com>
 *}

  {$M+}
  {$H+}
  {$IFDEF FPC}
  {$mode delphi}
  {$ENDIF}

Interface

Uses
    Classes, SysUtils, StrUtils;

Const
    sEndOfLine     = #$0A;
    sWinEndOfLine  = #$0D#$0A;
    sUnixEndOfLine = #$0A;
    sMacEndOfLine  = #$0D;

Type
    EmnStreamException = Class(Exception);

    TmnCustomStream = Class(TStream)
    private
        FBufferSize: Cardinal;
    published
    public
        Function WriteString(Const Value: String): Cardinal;
        Function WriteStream(Source: TStream): Longint;
        Property BufferSize: Cardinal read FBufferSize write FBufferSize;
    End;
  
    { TmnStream }

    TmnStream = Class(TmnCustomStream)
    private
        FStreamOwned: Boolean;
        FStream: TStream;
        FBuffer: PChar;
        FPos: PChar;
        FEnd: PChar;
        FEOF: Boolean;
        FEndOfLine: String;
        Procedure LoadBuffer;
        Procedure SetStream(Const Value: TStream);
    protected
    public
        Constructor Create(AStream: TStream; AEndOfLine: String;
            Owned: Boolean = True); overload; virtual; 
        Constructor Create(AStream: TStream; Owned: Boolean = True); overload;
        Destructor Destroy; override;
        Function Read(Var Buffer; Count: Longint): Longint; override;
        Function Write(Const Buffer; Count: Longint): Longint; override;
        Procedure ReadUntil(Const UntilStr: String; Var Result: String;
            Var Matched: Boolean);
        Function Readln(Var S: String; ExcludeEOL: Boolean = True): Boolean;
            overload;
        Function Readln: String; overload;
        Procedure ReadStrings(Value: TStrings);
        Procedure ReadCommand(Var Command: String; Var Params: String);
        Function WriteStrings(Const Value: TStrings): Cardinal;
        Function Writeln(Const Value: String): Cardinal;
        Procedure WriteCommand(Const Command: String; Const Params: String = '');
        Property Stream: TStream read FStream write SetStream;
        Property StreamOwned: Boolean read FStreamOwned write FStreamOwned
            default False;
        Property Eof: Boolean read FEOF;
        Property EndOfLine: String read FEndOfLine write FEndOfLine;
    End;

Implementation

Const
    cBufferSize = 2048;

    { TmnStream }

Function TmnCustomStream.WriteString(Const Value: String): Cardinal;
Begin
    Result := Write(Pointer(Value)^, Length(Value));
End;

Function TmnCustomStream.WriteStream(Source: TStream): Longint;
Var
    aBuffer: PChar;
    n: Cardinal;
Begin
    {$IFDEF FPC}
    aBuffer := Nil;
    {$ENDIF}
    GetMem(aBuffer, BufferSize);
    Result := 0;
    Try
        Repeat
            n := Source.Read(aBuffer^, BufferSize);
            If n > 0 Then Write(aBuffer^, n);
            Inc(Result, n);
        Until (n < BufferSize);
    Finally
        FreeMem(aBuffer, BufferSize);
    End;
End;

Function TmnStream.Writeln(Const Value: String): Cardinal;
Begin
    Result := WriteString(Value + EndOfLine);
End;

Function TmnStream.WriteStrings(Const Value: TStrings): Cardinal;
Var
    i: Integer;
Begin
    Result := 0;
    For i := 0 To Value.Count - 1 Do
    Begin
        If Value[i] <> '' Then
        //stupid delphi always add empty line in last of TStringList
        Result := Result + Writeln(Value[i]);
    End;
End;

Procedure TmnStream.ReadCommand(Var Command, Params: String);
Var
    s: String;
    p: Integer;
Begin
    {$IFDEF FPC}
    S := '';
    {$ENDIF}
    Readln(S);
    p := Pos(' ', s);
    If p > 0 Then
    Begin
        Command := Copy(s, 1, p - 1);
        Params  := Copy(s, p + 1, MaxInt);
    End
    Else
    Begin
        Command := s;
        Params  := '';
    End;
End;

Function TmnStream.Readln: String;
Begin
    Readln(Result);
End;

Procedure TmnStream.WriteCommand(Const Command, Params: String);
Begin
    If Params <> '' Then Writeln(Command + ' ' + Params)
    Else Writeln(Command);
End;

Function TmnStream.Readln(Var S: String; ExcludeEOL: Boolean = True): Boolean;
Var
    aMatched: Boolean;
Begin
    Result := not Eof;
    If Result Then
    Begin
        aMatched := False;
        ReadUntil(EndOfLine, S, aMatched);
        If ExcludeEOL and aMatched and (S <> '') Then
        S := LeftStr(S, Length(S) - Length(EndOfLine));
    End;
End;

Procedure TmnStream.ReadStrings(Value: TStrings);
Var
    S: String;
Begin
    While not Eof Do
    Begin
        If Readln(S) Then Value.Add(S);
    End;
End;

Function TmnStream.Write(Const Buffer; Count: Integer): Longint;
Begin
    Result := FStream.Write(Buffer, Count);
End;

{ TmnStream }

Constructor TmnStream.Create(AStream: TStream; AEndOfLine: String;
    Owned: Boolean = True);
Begin
    Inherited Create;
    If AStream = Nil Then Raise EmnStreamException.Create('Stream = nil');
    FStreamOwned := Owned;
    FStream := AStream;
    FBufferSize := cBufferSize;
    GetMem(FBuffer, FBufferSize);
    FPos := FBuffer;
    FEnd := FBuffer;
    FEndOfLine := AEndOfLine;
End;

Constructor TmnStream.Create(AStream: TStream; Owned: Boolean);
Begin
    Create(AStream, sEndOfLine, Owned);
End;

Destructor TmnStream.Destroy;
Begin
    If FStreamOwned Then FStream.Free;
    FreeMem(FBuffer, FBufferSize);
    FBuffer := Nil;
    Inherited;
End;

Procedure TmnStream.LoadBuffer;
Var
    aSize: Cardinal;
Begin
    If FPos < FEnd Then Raise EmnStreamException.Create('Buffer is not empty to load');
    FPos := FBuffer;
    aSize := FStream.Read(FBuffer^, FBufferSize);
    FEnd := FPos + aSize;
    If aSize = 0 Then FEOF := True;
End;

Function TmnStream.Read(Var Buffer; Count: Integer): Longint;
Var
    c, aCount: Longint;
    P: PChar;
Begin
    P := @Buffer;
    aCount := 0;
    While (Count > 0) and not Eof Do
    Begin
        c := FEnd - FPos;
        If c = 0 Then
        Begin
            LoadBuffer;
            Continue;//new
        End;
        If c > Count Then // is FBuffer enough for Count
        c := Count;
        Count  := Count - c;
        aCount := aCount + c;
        System.Move(FPos^, P^, c);
        Inc(P, c);
        Inc(FPos, c);
    End;
    Result := aCount;
End;

Procedure TmnStream.ReadUntil(Const UntilStr: String; Var Result: String;
    Var Matched: Boolean);
Var
    P: PChar;
    Function CheckBuffer: Boolean;
    Begin
        If not (FPos < FEnd) Then LoadBuffer;
        Result := (FPos < FEnd);
    End;
Var
    idx, l: Integer;
    t: String;
Begin
    If UntilStr = '' Then Raise Exception.Create('UntilStr is empty!');
    Idx := 1;
    Matched := False;
    l := Length(UntilStr);
    Result := '';
    While not Matched and CheckBuffer Do
    Begin
        P := FPos;
        While P < FEnd Do
        Begin
            If UntilStr[idx] = P^ Then Inc(Idx)
            Else Idx := 1;
            Inc(P);
            If Idx > l Then
            Begin
                Matched := True;
                Break;
            End;
        End;
        SetString(t, FPos, P - FPos);
        Result := Result + t;
        FPos   := P;
    End;
End;

Procedure TmnStream.SetStream(Const Value: TStream);
Begin
    If FStream <> Value Then
    Begin
        If (FStream <> Nil) and FStreamOwned Then FreeAndNil(FStream); 
        FStream      := Value;
        FStreamOwned := False;
    End;
End;

End.
